<template>
    <div class="PlateEdit" :ref="three.el">
        <div class="container-rb">{{ 'xx' }}</div>
        <div class="container-lb">
            <a-button @click="actions.clear" size="small" type="primary">清空</a-button>
            <a-button @click="actions.confirm" size="small" type="primary" style="margin-left: 5px"
                >确认</a-button
            >
        </div>
    </div>
</template>

<script setup>
    import { ref, reactive, onMounted, onUnmounted, computed } from 'vue';
    import * as THREE from 'three';
    import Stats from 'three/examples/jsm/libs/stats.module.js';
    import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
    import { message } from 'ant-design-vue';

    function convexHull2D(points) {
        const sorted = points.slice().sort((a, b) => (a.x === b.x ? a.y - b.y : a.x - b.x));
        const cross = (o, a, b) => (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);

        const lower = [];
        sorted.forEach((p) => {
            while (
                lower.length >= 2 &&
                cross(lower[lower.length - 2], lower[lower.length - 1], p) <= 0
            )
                lower.pop();
            lower.push(p);
        });

        const upper = [];
        sorted
            .slice()
            .reverse()
            .forEach((p) => {
                while (
                    upper.length >= 2 &&
                    cross(upper[upper.length - 2], upper[upper.length - 1], p) <= 0
                )
                    upper.pop();
                upper.push(p);
            });

        upper.pop();
        lower.pop();
        return lower.concat(upper);
    }

    // https://stackoverflow.com/questions/29884485/threejs-canvas-size-based-on-container
    function getDomSize(dom) {
        return { width: dom.parentNode.clientWidth, height: dom.clientHeight };
    }

    const emit = defineEmits(['confirm']);

    let requestId = null;
    let orbitControls = null;

    const three = {
        el: ref(),
        scene: null,
        camera: null,
        renderer: null,
        canvasWidth: 0,
        canvasHeight: 0,
        resizeObserver: null, // 监听 canvas 大小变化
        stats: null, // 帧率监测
        pointsGroup: new THREE.Group(), // 用于添加圆球（表示点）和线
        lineGroup: new THREE.Group(), // 用于添加封闭折线
        plateGroup: new THREE.Group() // 用于添加打印板
    };

    // 添加打印底板
    const addPlate = () => {
        const generatePlate = () => {
            const length = 200;
            const plate = new THREE.Group();

            // gap=10 的网格
            const color = 0xaaaaaa;
            const divisions = length / 10;
            const gridHelper = new THREE.GridHelper(length, divisions, color, color); // xz 平面
            gridHelper.rotation.x = Math.PI / 2;
            plate.add(gridHelper);

            return plate;
        };

        const plate = generatePlate();
        three.plateGroup.add(plate);
    };

    const addAxes = () => {
        const length = 130;
        const origin = new THREE.Vector3(0, 0, 0);
        const infos = [
            // x
            { dir: new THREE.Vector3(1, 0, 0), color: 0xff0000 },
            // y
            { dir: new THREE.Vector3(0, 1, 0), color: 0x00ff00 }
        ];
        infos.forEach(({ dir, color }) => {
            const arrowHelper = new THREE.ArrowHelper(dir, origin, length, color);

            // 显示在最上面：解决和其他 line 重合引起 z-fighting
            arrowHelper.cone.material.depthTest = false;
            arrowHelper.line.material.depthTest = false;

            // 增加立体感
            arrowHelper.cone.material = new THREE.MeshStandardMaterial({
                color,
                metalness: 0.6,
                roughness: 0.3
            });

            three.scene.add(arrowHelper);
        });
    };

    const refreshLine = () => {
        // 移除旧线
        let oldLine = three.lineGroup.children[0];
        if (oldLine) {
            three.lineGroup.remove(oldLine);
            oldLine.geometry.dispose();
            oldLine.material.dispose();
        }

        if (state.points.length > 2) {
            const hullPoints = convexHull2D(state.points); // 返回按外边沿顺序的顶点

            // 绘制封闭折线
            const geometry = new THREE.BufferGeometry().setFromPoints(
                hullPoints.map((p) => new THREE.Vector3(p.x, p.y, 0)) // 保持 z=0
            );
            const material = new THREE.LineBasicMaterial({ color: 0xff0000 });
            const line = new THREE.LineLoop(geometry, material);
            three.lineGroup.add(line);
        }
    };

    const state = reactive({
        points: []
    });

    onMounted(() => {
        // setup
        actionsThree.setup();
        actionsThree.setupGroup();
        // actionsThree.setupStats();
        actionsThree.setupResizeListener();

        // three.scene.add(new THREE.HemisphereLight(0xffffff, 0x222222, 1.75));
        orbitControls = new OrbitControls(three.camera, three.renderer.domElement);
        orbitControls.enableRotate = false;

        actionsThree.animate();

        addPlate(); // 添加网格
        addAxes(); // 添加坐标轴

        // 添加点击事件，计算与 z=0 平面的交点
        const raycaster = new THREE.Raycaster();
        const pointer = new THREE.Vector2();

        // 定义一个 z=0 的平面（法线朝向 +Z）
        const plane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0);

        three.renderer.domElement.addEventListener('click', (event) => {
            const rect = three.renderer.domElement.getBoundingClientRect();
            pointer.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
            pointer.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;

            raycaster.setFromCamera(pointer, three.camera);

            const point = new THREE.Vector3();
            raycaster.ray.intersectPlane(plane, point);

            // 在点击位置添加一个红色小球
            const sphere = new THREE.Mesh(
                new THREE.SphereGeometry(2, 16, 16),
                new THREE.MeshStandardMaterial({ color: 0xff0000 })
            );
            sphere.position.copy(point);
            three.pointsGroup.add(sphere);

            state.points.push(point.clone());
            refreshLine();
        });
    });

    onUnmounted(() => {
        actionsThree.dispose();
    });

    const actionsThree = {
        setup() {
            const { width, height } = getDomSize(three.el.value);

            three.canvasWidth = width;
            three.canvasHeight = height;

            // 透视相机
            three.camera = new THREE.PerspectiveCamera(45, width / height, 1, 10000);
            three.camera.position.set(0, 0, 320);
            three.camera.lookAt(0, 0, 0);

            three.renderer = new THREE.WebGLRenderer({
                antialias: true,
                alpha: true,
                logarithmicDepthBuffer: true
            });
            three.renderer.setPixelRatio(window.devicePixelRatio); // 显示模糊，要设置
            three.renderer.setSize(width, height);
            three.renderer.setClearColor(0xdddddd, 1);
            // renderer.outputEncoding = THREE.sRGBEncoding;
            three.renderer.autoClear = false;

            three.scene = new THREE.Scene();
            three.scene.background = new THREE.Color(0xdddddd);
            three.scene.add(three.camera);

            three.el.value.appendChild(three.renderer.domElement);

            // light
            let ambit = new THREE.AmbientLight(0xffffff, 2.8);
            three.scene.add(ambit);

            const light = new THREE.DirectionalLight(0xffffff, 0.5);
            light.position.set(0, 1, 0);
            three.scene.add(light);

            const directionalLight = new THREE.DirectionalLight(0xffffff, 0.25);
            directionalLight.position.set(0, 0, 1);
            three.scene.add(directionalLight);
        },
        setupGroup() {
            three.scene.add(three.pointsGroup);
            three.scene.add(three.lineGroup);
            three.scene.add(three.plateGroup);
        },
        setupStats() {
            // 创建性能监视器
            three.stats = new Stats();
            // 设置监视器面板，传入面板id（0: fps, 1: ms, 2: mb）
            three.stats.setMode(0);
            // 设置监视器位置
            three.stats.domElement.style.position = 'absolute';
            three.stats.domElement.style.width = '80px';
            three.stats.domElement.style.height = '48px';
            three.stats.domElement.style.left = '5px';
            three.stats.domElement.style.top = '5px';
            // 将监视器添加到页面中
            three.el.value.appendChild(three.stats.domElement);
        },
        animate() {
            requestId = requestAnimationFrame(actionsThree.animate);
            // three.renderer.clear(true, true, true);
            three.renderer.render(three.scene, three.camera);
            // three.stats?.update();
        },
        resizeWindow() {
            if (!three.el.value) return;
            const { width, height } = getDomSize(three.el.value);
            // console.log('resizeWindow: ', width, height);
            if (width * height !== 0) {
                three.canvasWidth = width;
                three.canvasHeight = height;
                three.renderer.setSize(width, height, true);
                // 正交相机自适应 resize：https://blog.csdn.net/yinge0508/article/details/123114184
                three.camera.aspect = width / height;
                three.camera.updateProjectionMatrix();
                // three.camera.lookAt(0, 0, 0);
            }
        },
        setupResizeListener() {
            three.resizeObserver = new ResizeObserver((entries) => {
                for (const entry of entries) {
                    actionsThree.resizeWindow();
                }
            });
            three.resizeObserver.observe(three.el.value);
            // resize 存在 bug: https://stackoverflow.com/questions/29884485/threejs-canvas-size-based-on-container
            window.addEventListener('resize', actionsThree.resizeWindow, true);
        },
        dispose() {
            requestId && cancelAnimationFrame(requestId);
            three.resizeObserver.disconnect();
            window.removeEventListener('resize', actionsThree.resizeWindow, true);
            // 释放 webgl context
            three.renderer.dispose();
            three.renderer.forceContextLoss();
            three.renderer.context = null;
            three.renderer.domElement = null;
            three.renderer = null;
        }
    };

    const actions = {
        clear() {
            state.points = [];
            three.pointsGroup.clear();
            three.lineGroup.clear();
        },
        confirm() {
            const hullPoints = convexHull2D(state.points); // 返回按外边沿顺序的顶点
            if (hullPoints.length < 3) {
                message.info('至少添加三个点');
                return;
            }
            emit('confirm', hullPoints);
        }
    };
</script>

<style lang="scss" scoped>
    .PlateEdit {
        position: absolute;
        left: 0;
        right: 0;
        top: 0;
        bottom: 0;

        .container-rb {
            position: absolute;
            right: 5px;
            bottom: 5px;
            color: black;
        }

        .container-lb {
            position: absolute;
            bottom: 0;
            left: 0;
            padding: 3px;
        }
    }
</style>
